home *** CD-ROM | disk | FTP | other *** search
/ HPAVC / HPAVC CD-ROM.iso / PCASM2.ZIP / BAS2-2.DOC < prev    next >
Text File  |  1990-08-01  |  42KB  |  941 lines

  1.  
  2.  
  3.  
  4.              The PC Assembler Tutor                                    mmxviii
  5.              ______________________
  6.  
  7.              If you now load the user library along with BASIC, you can look
  8.              at the segments and offsets of the arrays. Here is a program with
  9.              a couple of arrays:
  10.  
  11.                      **********************************************
  12.                      k% = 12000
  13.                      DIM   array1!(k%), array2!(k%), array3!(12000)
  14.               
  15.                      value1! = VARPTR (array1!(0))
  16.                      value2! = VARPTR (array2!(0))
  17.                      value3! = VARPTR (array3!(0))
  18.                      PRINT value1!, value2!, value3!
  19.                      **********************************************
  20.  
  21.              Each array is 12001 * 4 (bytes) or 48004 bytes long. The first
  22.              two have been defined as dynamic, so they can be anywhere in
  23.              memory as long as they're after the start of DS. The third one is
  24.              static, so it must be entirely in DS. Here's the output:
  25.  
  26.                     68032         116064        6252
  27.  
  28.              Remember, these offsets are not relative to the start of memory,
  29.              they are relative to the start of the DS segment. The DS segment
  30.              only goes up to offset 65535 so the first two are outside of DS
  31.              while the third one is totally inside (6252 + 48004 = 54006 which
  32.              is less than 65536). PTR86 is going to give us a SEGMENT:OFFSET
  33.              pair relative to the start of memory.{1} The form for the call
  34.              is:
  35.  
  36.                  CALL  PTR86 (segment%, offset%, value!)
  37.  
  38.              where value! is the number returned by VARPTR. 
  39.  
  40.              We'll add the following code to the bottom of the above program:
  41.  
  42.                      ***************************************
  43.                      CALL  PTR86 (  seg1% , off1%, value1! )
  44.                      CALL  PTR86 (  seg2% , off2%, value2! )
  45.                      CALL  PTR86 (  seg3% , off3%, value3! )
  46.               
  47.                      PRINT  seg1%, off1%
  48.                      PRINT  seg2%, off2%
  49.                      PRINT  seg3%, off3%
  50.                      ***************************************
  51.  
  52.              What does the program print now?
  53.  
  54.                   68032         116064        6252
  55.              ____________________
  56.  
  57.                 1. If you are using QuickBasic 4.0 or later this has become
  58.              simplified. You can just use VARSEG and VARPTR. These will give
  59.              you a segment offset pair which is usable in a subroutine call.
  60.  
  61.              ______________________
  62.  
  63.              The PC Assembler Tutor - Copyright (C) 1990 Chuck Nelson
  64.  
  65.  
  66.  
  67.  
  68.              BASIC II - Interfacing BASIC With Assembler                 mmxix
  69.              ___________________________________________
  70.  
  71.                   19125         0
  72.                   22127         0
  73.                   15263         12
  74.  
  75.              PTR86 has calculated the segments. The first two offsets are 0
  76.              and the third one is 12. Even though the third array is in the DS
  77.              segment, PTR86 has recalculated the segment to find the highest
  78.              segment which contains the first byte of the data. If you want to
  79.              do a little calculation, you can figure out that while this
  80.              program was running, DS was set to segment 14873. 
  81.  
  82.              VARPTR and PTR86 can be used to do this calculation for any array
  83.              element, not just array(0). Here's VARPTR:
  84.  
  85.                      value1! = VARPTR (array1!(0))
  86.                      value2! = VARPTR (array1!(196))
  87.                      value3! = VARPTR (array1!(2781))
  88.                      PRINT value1!, value2!, value3!
  89.  
  90.  
  91.              And here's the output:
  92.  
  93.                     68032         68816         79156
  94.  
  95.              From now on, we will have VARPTR inside of the PTR86 call:
  96.  
  97.                         CALL  PTR86 (  seg1% , off1%, VARPTR (array1!(0)) )
  98.                         CALL  PTR86 (  seg2% , off2%, VARPTR (array2!(0)) )
  99.                         CALL  PTR86 (  seg3% , off3%, VARPTR (array3!(0)) )
  100.  
  101.  
  102.              It is clearer and uses less space. Of course, we can use this for
  103.              any element in the array, not just the beginning of the array:
  104.  
  105.                         CALL  PTR86 (  seg1% , off1%, VARPTR (array1!(0)) )
  106.                         CALL  PTR86 (  seg2% , off2%, VARPTR (array1!(5076)) )
  107.                         CALL  PTR86 (  seg3% , off3%, VARPTR (array1!(1983)) )
  108.  
  109.  
  110.  
  111.              We now have all the ammunition we need to do a quicker disk
  112.              write. We can pass the offset address of any numeric data in the
  113.              DS segment, we can pass the string descriptor address of any
  114.              string, and we can pass the SEGMENT:OFFSET pair of any array
  115.              anywhere. 
  116.  
  117.              Before doing our disk write program, I need to say something
  118.              about subroutine calls. You notice that when I show a subroutine
  119.              call I am always showing what numeric type I am passing. There is
  120.              a reason for this. Let's step back from assembler for a minute
  121.              and do a BASIC program with a subroutine. Here's the program:
  122.  
  123.                  **********************************************************
  124.                  floatA! = 27.925
  125.                  floatB! = 16.96
  126.                  integerA% = 300
  127.                  integerB% = 140
  128.  
  129.  
  130.  
  131.  
  132.              The PC Assembler Tutor                                       mmxx
  133.              ______________________
  134.  
  135.                  CALL CheckTheNumbers (integerA%, integerB%, floatA!, floatB!)
  136.                  PRINT floatA!, floatB!, integerA%, integerB%
  137.                  END
  138.  
  139.                  SUB  CheckTheNumbers ( int1%, int2%, flt1!, flt2!) STATIC
  140.                          int1% = int1% + 7
  141.                          int2% = int2% * 45
  142.                          flt1! = flt1! + 19.0
  143.                          flt2! = flt2! * 43.0
  144.                  END SUB
  145.                  ***********************************************************
  146.  
  147.              This gives the following output:
  148.  
  149.                  46.925        729.28        307           6300
  150.  
  151.              This is nothing earthshaking. Now let's change one line in the
  152.              program:
  153.  
  154.                  CALL CheckTheNumbers (floatA!, floatB!, integerA%, integerB%)
  155.  
  156.              In the call statement I have put single precision numbers where
  157.              the integers were and integers where the single precision numbers
  158.              were. Now let's look at the output:
  159.  
  160.                  Overflow.
  161.                  ENTER to debug, SPACEBAR to edit
  162.  
  163.              We had an overflow. Here's where the debugger said the error was:
  164.  
  165.                            int2% = int2% * 45
  166.  
  167.              But int2% received the floating point number for floatB!, and
  168.              that is 16.96. Since 16.96 * 45 = 763.2, where was the overflow?
  169.              What the subroutine saw was not 16.96 but the first two binary
  170.              bytes of floatB! (since we passed the address of floatB!). It
  171.              thought these were an integer, performed a multiplication and got
  172.              an error because the result was too big for an integer. Now,
  173.              change the value in floatB! to:
  174.  
  175.                  floatB! = 1.696E-29
  176.  
  177.              and run the program again. Your results should be:
  178.  
  179.                   27.92501      1.693756E-29  0             16792
  180.  
  181.              These numbers have no relation to either what we started out with
  182.              or what we did. Why? Because the subroutine mixed binary
  183.              information from single precision numbers with binary information
  184.              from integers and came up with unmitigated garbage. It was not
  185.              only doing that, it was also writing 4 bytes of information into
  186.              a 2 byte integer. Both flt1! and flt2! were writing past the end
  187.              of the data and overwriting something else.
  188.  
  189.              There is NEVER any checking between a subroutine and the calling
  190.              program to see that the correct numeric types are being passed
  191.              (integers, long integers, single precision, double precision). In
  192.  
  193.  
  194.  
  195.  
  196.              BASIC II - Interfacing BASIC With Assembler                 mmxxi
  197.              ___________________________________________
  198.  
  199.              C and Pascal this checking is done by the compiler at compile
  200.              time and the compiler will howl if you try to do something like
  201.              this. In BASIC (my BASIC at least), this checking is not being
  202.              done. Therefore, it is IMPERATIVE that you make sure that you
  203.              pass the correct data types. 
  204.  
  205.              Having given that warning, we are going to build an assembler
  206.              subprogram that opens a file for writing, then writes a block of
  207.              data from memory to disk. The form of the call will be:
  208.  
  209.              CALL BlockToDisk ("filename$"+CHR$(0), seg%, offset%, # of bytes)
  210.  
  211.              Notice that there MUST be a 0 after the filename. When you use
  212.              this function, you must always have:
  213.  
  214.                  filename$ = "my.file.name" + CHR$(0)
  215.  
  216.              The disk interrupt that is used in this subroutine expects a 'C'
  217.              string (terminated by a number 0, not an ASCII character '0'). If
  218.              you don't do it, you will almost certainly get an error.
  219.  
  220.              This is followed by the segment of the first byte of data, the
  221.              offset of the first byte of data, and the number of BYTES (not
  222.              array elements) to write.
  223.  
  224.              Which way does BASIC load the arguments to a subroutine? From
  225.              left to right, just like PASCAL. Also, in BASIC, ALL subroutine
  226.              calls are far calls. Therefore, BASIC will do the following when
  227.              it calls BlockToDisk:
  228.  
  229.                       PUSH address of file_descriptor
  230.                       PUSH address of block segment
  231.                       PUSH address of block offset
  232.                       PUSH address of length
  233.                       CALL FAR PTR BlockToDisk
  234.  
  235.              There are two things to notice here. First, these are all
  236.              ADDRESSES of the data, not the data items themselves. Upon entry
  237.              to the subroutine and initialization of BP, the stack will look
  238.              like this:
  239.  
  240.                       address of file_descriptor    bp+12
  241.                       address of block segment      bp+10
  242.                       address of block offset       bp+8
  243.                       address of length             bp+6
  244.                       old CS                        bp+4
  245.                       old IP                        bp+2
  246.                  BP-> old BP                        bp
  247.  
  248.              Secondly, the name of the subroutine does not have any periods.
  249.              BASIC allows periods '.' but does not allow underscores '_' while
  250.              assembler allows underscores but doesn't allow periods. (Periods
  251.              have a special meaning in assembler; they are used in
  252.              structures).
  253.  
  254.              In order to go on from here, you need a book about interrupts.
  255.              This information is from "DOS Programmer's Reference" by Terry
  256.  
  257.  
  258.  
  259.  
  260.              The PC Assembler Tutor                                     mmxxii
  261.              ______________________
  262.  
  263.              Dettmann, but if you have "The Peter Norton Programmer's Guide to
  264.              The IBM PC", that's fine too. I'm going to give only partial
  265.              information about these interrupts and you should have complete
  266.              information.
  267.  
  268.              Here's the program. The explaination will come afterwards.
  269.  
  270.              *******************************************************
  271.              ; BASOUT.ASM 
  272.              include \pushregs.mac 
  273.              PUBLIC  BLOCKTODISK 
  274.              DGROUP  GROUP _DATA 
  275.              ; - - - - - - - - - - - - - - - - - - - - - 
  276.              _DATA  SEGMENT PUBLIC 'DATA' 
  277.              file_handle     dw      ? 
  278.              error_message   db      "Disk i/o error #" 
  279.              error_byte      db      "   ", 13, 10, "$" 
  280.              _DATA  ENDS 
  281.              ; - - - - - - - - - - - - - - - - - - - - - 
  282.              _TEXT  SEGMENT 'CODE' 
  283.                      ASSUME cs:_TEXT, ds:DGROUP 
  284.              ; - - - - - - - - - - - - - - - - - - - - -  
  285.              print_error  proc far 
  286.                      mov     ah, 9           ; print error message 
  287.                      mov     dx, offset DGROUP:error_message 
  288.                      int     21h 
  289.                      ret 
  290.              print_error  endp 
  291.              ; - - - - - - - - - - - - - - - - - - - - - - - - -  
  292.              ; BlockToDisk ( filename , array SEG, array OFF, # of bytes) 
  293.              ; this is for BASIC 
  294.               
  295.                      DESCRIPTOR_LOCATION  EQU    [bp + 12] 
  296.                      SEGMENT_LOCATION     EQU    [bp + 10] 
  297.                      OFFSET_LOCATION      EQU    [bp + 8] 
  298.                      LENGTH_LOCATION      EQU    [bp + 6] 
  299.               
  300.              BLOCKTODISK proc far 
  301.                      push    bp 
  302.                      mov     bp, sp 
  303.                      PUSHREGS    ax, bx, cx, dx, si, ds 
  304.               
  305.                      ; open a new file or truncate an old one 
  306.                      mov     si, DESCRIPTOR_LOCATION 
  307.                      mov     ah, 3Ch         ; open new or truncate old 
  308.                      mov     cx, 0           ; normal file attribute 
  309.                      mov     dx, [si+2]      ; [si] =length, [si+2] =location
  310.                      int     21h 
  311.                      jnc     write_the_file  ; ok if CF=0, error if CF=1
  312.               
  313.                      mov     error_byte, '1' ; cannot open 
  314.                      call    print_error 
  315.                      jmp     exit 
  316.               
  317.              write_the_file: 
  318.                      mov     file_handle, ax ; store handle for later use 
  319.                      mov     bx, ax          ; file_handle to bx 
  320.  
  321.  
  322.  
  323.  
  324.              BASIC II - Interfacing BASIC With Assembler               mmxxiii
  325.              ___________________________________________
  326.  
  327.                      mov     ah, 40h         ; int 21h ah = 40h, write block 
  328.                      mov     si, LENGTH_LOCATION 
  329.                      mov     cx, [si]        ; # of bytes into CX
  330.                      push    ds              ; save BASIC's DS 
  331.                      mov     si, OFFSET_LOCATION   
  332.                      mov     dx, [si]        ; offset to DX 
  333.                      mov     si, SEGMENT_LOCATION   
  334.                      mov     ds, [si]        ; segment to DS 
  335.                      int     21h 
  336.                      pop     ds              ; restore BASIC's DS 
  337.                      jnc     normal_exit     ; ok if CF=0, error if CF=1
  338.                      mov     error_byte, '2' ; bad file write 
  339.                      call    print_error 
  340.               
  341.              normal_exit: 
  342.                      mov     ah, 3Eh         ; close the file 
  343.                      mov     bx, file_handle 
  344.                      int     21h 
  345.               
  346.              exit: 
  347.                      POPREGS    ax, bx, cx, dx, si, ds 
  348.                      mov     sp, bp 
  349.                      pop     bp 
  350.                      ret     (8)      ; pop 4 words (8 bytes off the stack) 
  351.               
  352.              BLOCKTODISK  endp 
  353.              ; - - - - - - - - - - - - - - - - - - - - - - - - -  
  354.              _TEXT  ENDS 
  355.              END    
  356.              **************************************************************
  357.  
  358.              This is using the standardized segment names so the data will be
  359.              in the DS segment. Notice the DGROUP GROUP declaration. Also
  360.              notice that in the 'print_error' subroutine, we have done an
  361.              offset override with 'offset DGROUP:error_message'. You need to
  362.              do this every time to avoid errors with the offset addressing
  363.              whenever you are using the DGROUP GROUP directive. Go back to the
  364.              discussion of simplified segment directives if you don't remember
  365.              this.
  366.  
  367.              The main program has 3 interrupts. The first one opens a new file
  368.              or truncates an old one to zero length. The file will be usable
  369.              for reading and/or writing:
  370.  
  371.                  Int  21h   function 3Ch
  372.                  AH = 3Ch
  373.                  CX = 0     0 indicates a normal file
  374.                  DS:DX      address of ASCII filename (terminated by 0)
  375.  
  376.                  Returns:
  377.                            AX = file handle if CF = 0
  378.                       or:  AX = error code if CF = 1
  379.  
  380.              This filename can be any legitimate pathname specification, and
  381.              must be terminated by a zero. Like all the disk interrupts we
  382.              will see in this chapter, if there is an error, the interrupt
  383.              will set CF = 1. Otherwise it will clear CF = 0. If CF = 0 you
  384.  
  385.  
  386.  
  387.  
  388.              The PC Assembler Tutor                                     mmxxiv
  389.              ______________________
  390.  
  391.              can go on; if CF = 1, there was an error and you need to
  392.              terminate the subroutine and do some error reporting. The file
  393.              handle is a number from 0 to 65535 which the operating system
  394.              gives your program to uniquely identify that open file. There is
  395.              no other open file in the system which has that number. Guard it
  396.              carefully because it is your ONLY access to the file.
  397.  
  398.              The second interrupt writes a block of data to disk.
  399.  
  400.                  Int 21h   function 40h
  401.                  AH = 40h
  402.                  BX = file handle
  403.                  CX = number of bytes to write
  404.                  DS:DX = address of first byte of data
  405.  
  406.                  Returns:
  407.                       AX = actual number of bytes written if CF = 0
  408.                       AX = error code if CF = 1
  409.  
  410.              This too sets the carry flag if there was an error and clears it
  411.              if there wasn't. It is limited to writing 65535 bytes at a time,
  412.              but the largest array we can have is 65535 bytes (actually 65534
  413.              since all data types have an even number of bytes), so this is no
  414.              problem.
  415.  
  416.              The third interrupt closes the file.
  417.  
  418.                  Int 21h   function 3Eh
  419.                  AH = 3Eh
  420.                  BX = File handle
  421.  
  422.              Also, the print-error subroutine has an interrupt
  423.  
  424.                  Int 21h   function 09h
  425.                  AH = 9
  426.                  DS:DX = first byte of string.
  427.  
  428.              This string must be terminated by a dollar sign '$' (of all
  429.              things). The message is on two lines so we can insert an error
  430.              number into the middle of the message. This is a quick and dirty
  431.              interrupt for string printing.
  432.  
  433.              All interrupt numbers and function numbers are hex. This is
  434.              standard for interrupts. If things go wierd, always check first
  435.              to make sure that you have a hex number and not a decimal number.
  436.  
  437.              The data has an 'ASSUME ds:DGROUP' statement. 
  438.  
  439.              Like Pascal, BASIC requires that the CALLED subroutine pop the
  440.              arguments off the stack, so we pop 4 extra words (8 extra bytes)
  441.              with:
  442.  
  443.                  ret  (8)
  444.  
  445.              Assemble this program and put the object file in a library with
  446.              the other object files by using BUILDLIB.EXE. Now all we need is
  447.              a BASIC program to use this. Here it is:
  448.  
  449.  
  450.  
  451.  
  452.              BASIC II - Interfacing BASIC With Assembler                 mmxxv
  453.              ___________________________________________
  454.  
  455.  
  456.                  ************************************************************
  457.                  DIM   large.array! (10000) 
  458.               
  459.                  FOR i% = 1 to 10000 
  460.                        large.array! (i%) = 2.167832E+19 
  461.                  NEXT i% 
  462.               
  463.                  filename$ = "blocktxt.doc" + CHR$ (0) 
  464.                  length% = 40000 - 65536 
  465.                  PRINT time$ 
  466.                  CALL PTR86 (segment%, offset%, VARPTR (large.array!(1)) ) 
  467.                  CALL BlockToDisk ( filename$ , segment% , offset%, length% )
  468.                  PRINT 
  469.                  PRINT time$ 
  470.                  ************************************************************
  471.  
  472.              There is an extra PRINT statement there which I will explain
  473.              later. We are starting at large.array!(1) because that is where
  474.              we started with the other programs. why are we subtracting 65536?
  475.              Because BASIC has a limit of -32768 to +32767, so we store 40000
  476.              as its modular equivalent (mod 65536).
  477.  
  478.              How long does the disk write take? From 2 to 3 seconds, and much
  479.              of that time was spent opening and truncating the file. This is
  480.              significantly better than the other ways of doing i/o. In fact,
  481.              the limits of this routine are the limits of your system. It is
  482.              literally impossible to do disk i/o any faster than this. 
  483.  
  484.              Try using a filename that doesn't have a CHR$(0) at the end. You
  485.              should get an error message. In my BASIC, here is the output:
  486.  
  487.                  19:50:23
  488.                  Disk i/o error #1
  489.                  19:50:23
  490.  
  491.              Now remove that lone PRINT statement (the next to the last line).
  492.              Here's my output:
  493.  
  494.                  19:50:00
  495.                  D9:50:00 error #1
  496.                  1
  497.  
  498.              For the QuickBASIC 3.0 environment, BASIC thinks that it has
  499.              complete control of screen i/o, so it is not doing its i/o in a
  500.              standard way and is overwriting the error message. If you are
  501.              going to do any screen i/o from assembler, you will have to think
  502.              of a way to live in harmony with BASIC.{2} We simply trick BASIC
  503.              into writing an empty line where the message was. This may not
  504.              always work correctly, especially if the window is scrolling up.
  505.  
  506.              ____________________
  507.  
  508.                 2. The easiest way to do this is to save the whole screen
  509.              image and cursor location, do what you want using the whole
  510.              screen, and then restore the screen and the cursor before
  511.              returning.
  512.  
  513.  
  514.  
  515.  
  516.              The PC Assembler Tutor                                     mmxxvi
  517.              ______________________
  518.  
  519.              This whole program only involved interrupts. There is nothing
  520.              intrinsically assembler-like in its capabilities. In fact, we'll
  521.              do its disk read counterpart entirely in BASIC. 
  522.  
  523.              Let's do something that requires assembler language. The BASIC
  524.              program:
  525.                  *******************************************
  526.                  FOR i% = 1 to 10000
  527.                       to.array!(i%) = from.array!(i%)
  528.                  NEXT i%
  529.                  *******************************************
  530.  
  531.              get's the job done, but is isn't all that fast. It requires about
  532.              5.5 seconds. This is a natural for assembler. Dive down into the
  533.              assembler level, move the string, and come back up for air. Our
  534.              BASIC program will be:
  535.  
  536.                  *******************************************
  537.                  n% = 10000 
  538.                  DIM from.array! (n%), to.array! (n%) 
  539.               
  540.                  PRINT time$ 
  541.                  FOR i% = 1 to 10000 
  542.                       to.array!(i%) = from.array!(i%) 
  543.                  NEXT i% 
  544.                  PRINT time$ 
  545.                 FOR j% = 1 to 50 
  546.                  cnt% = 40000 - 65536     'count
  547.                  CALL PTR86 (from.seg%, from.off%, VARPTR( from.array!(1)) ) 
  548.                  CALL PTR86 (to.seg%, to.off%, VARPTR ( to.array!(1)) ) 
  549.                  CALL BlockMove(from.seg%, from.off%, to.seg%, to.off%, cnt%)
  550.                 NEXT j% 
  551.                  PRINT time$ 
  552.                  ********************************************
  553.  
  554.              We are doing 50 repeats of the bottom section of code so you will
  555.              be able to average the time. Here's the assembler program:
  556.  
  557.              ; - - - - - - - - - -
  558.              include /pushregs.mac 
  559.              _TEXT SEGMENT PUBLIC 'CODE' 
  560.                      ASSUME cs:_TEXT 
  561.                      PUBLIC BlockMove 
  562.              ; - - - - - - - - - -  
  563.              ; BlockMove ( from.seg, from.off, to.seg, to.off, byte.count) 
  564.              ; for BASIC 
  565.              ; MOVSW is from DS:[SI] to ES:[DI] 
  566.               
  567.                      FROM_SEG_ADDRESS        EQU     [bp+14] 
  568.                      FROM_OFFSET_ADDRESS     EQU     [bp+12] 
  569.                      TO_SEG_ADDRESS          EQU     [bp+10] 
  570.                      TO_OFFSET_ADDRESS       EQU     [bp+8] 
  571.                      BYTE_COUNT_ADDRESS      EQU     [bp+6] 
  572.              ; - - - - - - - - - - 
  573.              BlockMove proc far 
  574.                      push    bp 
  575.                      mov     bp, sp 
  576.  
  577.  
  578.  
  579.  
  580.              BASIC II - Interfacing BASIC With Assembler               mmxxvii
  581.              ___________________________________________
  582.  
  583.                      PUSHREGS  ax, bx, cx, dx, si, di, es, ds 
  584.               
  585.                      mov     si, TO_SEG_ADDRESS 
  586.                      mov     es, [si]        ; to_seg to ES 
  587.                      mov     si, TO_OFFSET_ADDRESS 
  588.                      mov     di, [si]        ; to_offset to DI 
  589.                      mov     si, BYTE_COUNT_ADDRESS 
  590.                      mov     cx, [si]        ; byte count to CX 
  591.                      mov     si, FROM_SEG_ADDRESS 
  592.                      mov     ax, [si]        ; temporary storage for new DS 
  593.                      mov     si, FROM_OFFSET_ADDRESS 
  594.                      mov     si, [si]        ; from_offset to SI 
  595.                      mov     ds, ax          ; now move from_seg to DS 
  596.                      sub     bx, bx          ; clear BX 
  597.                      shr     cx, 1           ; divide by 2, remainder in CF 
  598.                      rcl     bx, 1           ; move CF to low bit of BX 
  599.                      cld                     ; clear DF (go up) 
  600.                      rep     movsw           ; the block move (count in CX) 
  601.                      and     bx, bx          ; one extra byte? 
  602.                      jz      exit 
  603.                      movsb                   ; move one last byte 
  604.               
  605.              exit: 
  606.                      POPREGS  ax, bx, cx, dx, si, di, es, ds 
  607.                      mov     sp, bp 
  608.                      pop     bp 
  609.                      ret     (10) 
  610.               
  611.              BlockMove endp 
  612.              ; - - - - - - - - - -  
  613.              _TEXT ENDS 
  614.              END   
  615.              ; - - - - - - - - - - 
  616.  
  617.              This is a string block move using MOVSW. The count is the number
  618.              of BYTES, not the number of array elements. CX contains the byte
  619.              count. It is divided by 2 so we can move words, and if there is a
  620.              remainder (i.e. if the number was odd), BX is set to 1. We move
  621.              words instead of bytes, and afterwards we check BX to see if we
  622.              need to move 1 byte more. This routine takes about 1/8 second
  623.              instead of 5.5 seconds. This is a considerable savings in time.
  624.              There is a small problem, however. If the FROM block and the TO
  625.              block overlap (e.g. move 400 bytes from array!(11) to
  626.              array!(26)), then the data may be compromised. To be exact, if
  627.              the start of the FROM data is below the start of the TO data, the
  628.              data will be screwed up. The general solution of this for BASIC
  629.              is in BLKMOVE.ASM, which is in a file called MISHMASH.DOC which
  630.              is in \XTRAFILE.
  631.  
  632.              Finally, here's the disk read done entirely in BASIC. Once again
  633.              you need your DOS interrupt book.
  634.  
  635.                  ********************************************************
  636.                  ' READBLK.BAS 
  637.                  ' reads a block from the disk into memory 
  638.               
  639.                  DIM in.regs%(9), out.regs%(9) 
  640.  
  641.  
  642.  
  643.  
  644.              The PC Assembler Tutor                                   mmxxviii
  645.              ______________________
  646.  
  647.                  DIM big.array! (10000) 
  648.               
  649.                  AX% = 0 
  650.                  BX% = 1 
  651.                  CX% = 2 
  652.                  DX% = 3 
  653.                  BP% = 4 
  654.                  SI% = 5 
  655.                  DI% = 6 
  656.                  FLGS% = 7 
  657.                  DS% = 8 
  658.                  ES% = 9 
  659.               
  660.                  filename$ = "blocktxt.doc" + CHR$(0) 
  661.               
  662.                  PRINT time$ 
  663.                  ' open an existing file for reading 
  664.                  in.regs%(AX%) = &H3D00 
  665.                  in.regs%(DX%) = SADD (filename$) 
  666.                  CALL INT86 (&H21,VARPTR (in.regs%(0)),VARPTR (out.regs%(0)))
  667.  
  668.                  IF  (out.regs%(FLGS%) AND &H0001)  <> 0  THEN 
  669.                       PRINT  "Can't open the file." 
  670.                       GOTO ExitProgram 
  671.                  END IF 
  672.               
  673.                  ' set the i/o pointer to 0 
  674.                  file.handle% = out.regs%(AX%) 
  675.                  in.regs%(AX%) = &H4200 
  676.                  in.regs%(BX%) = file.handle% 
  677.                  in.regs%(CX%) = 0 
  678.                  in.regs%(DX%) = 0 
  679.                  CALL INT86 (&H21, VARPTR(in.regs%(0)), VARPTR(out.regs%(0)))
  680.  
  681.                  IF  (out.regs%(FLGS%) AND &H0001)  <> 0   THEN 
  682.                       PRINT  "File pointer error" 
  683.                       GOTO  CloseFile 
  684.                  END IF 
  685.               
  686.                  in.regs%(AX%) = &H3F00 
  687.                  in.regs%(BX%) = file.handle% 
  688.                  in.regs%(CX%) = 40000 - 65536 
  689.                  CALL PTR86 ( segment%, offset%, VARPTR (big.array!(1) )) 
  690.                  in.regs%(DX%) = offset% 
  691.                  in.regs%(DS%) = segment% 
  692.                  CALL INT86X (&H21,VARPTR(in.regs%(0)),VARPTR(out.regs%(0))) 
  693.                  IF  (out.regs%(FLGS%) AND &H0001)  <> 0  THEN 
  694.                       PRINT  "Disk read error" 
  695.                  END IF 
  696.               
  697.               
  698.              CloseFile: 
  699.                  in.regs%(AX%) = &H3E00 
  700.                  in.regs%(BX%) = file.handle% 
  701.                  CALL INT86 (&H21, VARPTR(in.regs%(0)), VARPTR(out.regs%(0)))
  702.  
  703.                  PRINT time$ 
  704.  
  705.  
  706.  
  707.  
  708.              BASIC II - Interfacing BASIC With Assembler                mmxxix
  709.              ___________________________________________
  710.  
  711.               
  712.              ExitProgram: 
  713.                  END 
  714.                  ******************************************************
  715.  
  716.              This shows the use of INT86 in BASIC. We have two 10 element
  717.              integer arrays (0 - 9). One is used for putting the data into the
  718.              registers before the interrupt call and the other is used for
  719.              getting the data out of the registers after the interrupt. At the
  720.              top we have substituted variable names for the array elements
  721.              they represent. This is the only way to make sense of things in
  722.              BASIC. What is the difference between INT86 and INT86X? INT86X
  723.              also changes ES and DS. 
  724.  
  725.              We need a different file opening call because the last one
  726.              TRUNCATED the file. This one just opens a pre-existing file and
  727.              we put 00 in AL to signal a file read:
  728.  
  729.                  INT 21h   Function 3Dh
  730.                  AH = 3dh  ; open a pre-existing file
  731.                  AL = 0    ; file read
  732.                  DS:DX     ; pointer to a 00h terminated string
  733.  
  734.                  Returns
  735.                       AX = file handle if CF = 0
  736.                       AX = error code if CF = 1
  737.  
  738.              We use SADD to get the filename offset in DS because this is
  739.              exactly what DOS wants. SADD is a function that gives the offset
  740.              of a string (the string itself) relative to the DS segment. It
  741.              gives no length information. 
  742.  
  743.              At every step along the way we check CF to make sure it is 0 and
  744.              not 1. We need to make sure that the file pointer is at the
  745.              beginning of the file. This is:
  746.  
  747.                  INT 21h   Function 42h
  748.                  AH = 42h  ; move file pointer
  749.                  AL = 0    ; count from beginning of the file
  750.                  CX:DX = 0 ; 4 byte offset from beginning of file
  751.  
  752.                  Returns
  753.                       DX:AX = new file-pointer location if CF = 0
  754.                       AX = error code if CF = 1
  755.  
  756.              Then we do the block read:
  757.  
  758.                  INT 21h   Function 3Fh
  759.                  AH = 3Fh  ; read a block from disk
  760.                  BX = file handle
  761.                  CX = byte count
  762.                  DS:DX = pointer to first byte of block
  763.  
  764.                  Returns
  765.                       AX = # of bytes read (can be less than CX) if CF = 0
  766.                       AX = error code if CF = 1
  767.  
  768.  
  769.  
  770.  
  771.  
  772.              The PC Assembler Tutor                                      mmxxx
  773.              ______________________
  774.  
  775.              Finally, we close the file:
  776.  
  777.                  INT 21h   Function 3Eh
  778.                  AH = 3Eh  ; close a file
  779.                  BX = file handle
  780.  
  781.                  Returns
  782.                       nothing if CF = 0 (close was successful)
  783.                       AX = error code if CF = 1
  784.  
  785.              All you need to know about CF is that when the flags are
  786.              represented as a word (2 bytes) CF = &H0001.
  787.  
  788.              Is this faster than our other access methods? Our worst case
  789.              before took 79 seconds and this one takes 1 second. This is
  790.              certainly worth using for large disk reads. We don't need to go
  791.              down to the assembler level, either.
  792.  
  793.              What's the difference between this and bload? Bload requires that
  794.              the file has already been stored from memory. When you use BSAVE,
  795.              the binary information is written to disk, but the first seven
  796.              bytes is BLOAD information. The first byte (0FDh) is a signature
  797.              byte. The next six bytes are three words. (1) the segment where
  798.              the data came from, (2) the offset where the data came from, and
  799.              (3) the length of the data. Of course, having these seven bytes
  800.              in the front makes the file incompatible with everything else in
  801.              the world. It even makes it difficult to load the information
  802.              into BASIC the first time, since this seven byte header is
  803.              missing in the original data unless the data came from a BASIC
  804.              file.
  805.  
  806.  
  807.  
  808.              So what sorts of things are candidates for assembler subroutines?
  809.              Things that are cumbersome in BASIC. If you want the top byte of
  810.              a number, that's difficult. If you want to rotate the bits of a
  811.              number, that's extremely hard. Shifting bits is hard. Practically
  812.              everything involving unsigned numbers is problematic. For every
  813.              assembler instruction, if you can't do it easily in BASIC you
  814.              should make a subroutine that does it in assembler. How about one
  815.              that does unsigned division? Another subroutine that you might
  816.              want to make is one that returns both the quotient and the
  817.              remainder from signed division. This cuts the work in half if you
  818.              need both of them.
  819.  
  820.              Well, use BASIC any way you want, but most of all, have fun!
  821.  
  822.  
  823.  
  824.  
  825.  
  826.  
  827.  
  828.  
  829.  
  830.  
  831.  
  832.  
  833.  
  834.  
  835.  
  836.              BASIC II - Interfacing BASIC With Assembler                mmxxxi
  837.              ___________________________________________
  838.  
  839.  
  840.                                      SUMMARY
  841.  
  842.              BASIC strings are defined by STRING DESCRIPTORS. A string
  843.              descriptor is a 4 byte block that contains the LENGTH of the
  844.              string and its LOCATION in the DS segment. Though you may  modify
  845.              individual bytes of a string from the assembler level, you may
  846.              not alter the length without interfering with BASIC's memory
  847.              management system.
  848.  
  849.              BASIC passes all arguments by reference. That is it sends the
  850.              offset address of the data instead of the data itself. The ruiles
  851.              are:
  852.  
  853.                  1) If it is a single piece of numeric data, the offset is
  854.                  relative to the DS segment.
  855.  
  856.                  2) If it is a string, the address is the address of the
  857.                  STRING DESCRIPTOR which contains both the length and
  858.                  location of the string.
  859.  
  860.                  3) To reference an array, use VARPTR (array(0)) to get the
  861.                  offset and then PTR86 to convert this to a SEGMENT:OFFSET
  862.                  pair which is usable by the assembler subroutine.
  863.  
  864.                  4) If you pass a single array element array(x) instead of
  865.                  using VARPTR, BASIC will pass the location of that element,
  866.                  but the element might be separated from the rest of the
  867.                  array, so only pass an individual element if you want the
  868.                  element itself and not the array.{3}
  869.  
  870.              SADD (stringname$)
  871.                  SADD [Microsoft's string address function] is used to pass
  872.                  the offset address of stringname$ relative to BASIC's DS
  873.                  segment. It should only be used with 00h terminated strings
  874.                  since this gives no length information. It can, however, be
  875.                  used in conjunction with LEN (stringname$).
  876.  
  877.              number! = VARPTR (variable)
  878.                  In older BASICs, VARPTR gives the offset address of
  879.                  "variable" relative to the first byte of the DS segment.
  880.                  This variable can be anywhere in memory from DS:0000 to the
  881.                  end of memory, and the number returned will be a single
  882.                  precision number in the range 0 to 1,048,576.
  883.  
  884.              VARSEG and VARPTR
  885.                  In more recent BASICs, using a combination of VARSEG and
  886.              ____________________
  887.  
  888.                 3. The rule here is that if the array itself is outside of the
  889.              DS segment (if it is a dynamic array), BASIC will make a copy of
  890.              the element inside of DS before the CALL, give you the address of
  891.              the COPY, and return the copy to its appropriate place in the
  892.              array after the CALL. This copy can be hundreds of thousands of
  893.              bytes away from the actual array. If you want the element itself
  894.              this works properly, but if you want the array, the address will
  895.              be the wrong address.
  896.  
  897.  
  898.  
  899.  
  900.              The PC Assembler Tutor                                    mmxxxii
  901.              ______________________
  902.  
  903.                  VARPTR has supplanted the use of PTR86.
  904.  
  905.              PTR86 ( segment%, offset%, VARPTR (variable) )
  906.                  PTR86 [Microsoft's segmentation scheme] takes the result
  907.                  provided by VARPTR and adds it to DS to come up with a total
  908.                  address. It then converts this absolute address into a
  909.                  SEGMENT:OFFSET pair where the segment is the highest segment
  910.                  that contains the first byte of the variable from VARPTR and
  911.                  the offset is a number from 0 to 15 which is the offset of
  912.                  this variable in this segment. 
  913.  
  914.              RET (x)
  915.                  When executing a return, all called subroutines must pop the
  916.                  arguments passed to them by BASIC. The number of BYTES
  917.                  popped is twice the number of arguments (as long as you are
  918.                  passing addresses and not actual data).
  919.  
  920.              CALL MY_ROUTINE (arg1, arg2, arg3, etc)
  921.                  Arguments are always PUSHed on the stack from left to right,
  922.                  so this call will:
  923.  
  924.                       PUSH address of arg1
  925.                       PUSH address of arg2
  926.                       PUSH address of arg3
  927.                       etc.
  928.  
  929.                  in that order. 
  930.  
  931.              INT86 ( interrupt.number%, in.reg.array%(9), out.reg.array%(9) )
  932.                  INT86 executes a DOS interrupt (interrupt.number%). The
  933.                  integers in in.reg.array% are put into the arithmetic
  934.                  registers before the call and the arithmetic registers are
  935.                  put into out.reg.array after the call. INT86X does the same
  936.                  thing but also changes the DS and ES segment registers.
  937.                  Consult your BASIC manual for the proper ordering of the
  938.                  registers in the array. Neither of these changes CS, SS or
  939.                  SP.
  940.  
  941.